package chen.web.user;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.UUID;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.event.CacheEventListener;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import chen.web.ExecuteHandler;
import chen.web.Executor;
import chen.web.cache.CacheUtils;
import chen.web.config.ConfigRepository;
import chen.web.config.SiteConfig;
import chen.web.event.EventSupport;
import chen.web.user.event.OfflineEvent;
import chen.web.user.event.OnlineEvent;

/**
 * 提供系统与用户之间的会话功能．
 * 此功能不能提供给使用公共访客帐号的用户
 * @author chen
 *
 */
public class SessionSupport {
	
	public static final String SessionName = "session";
	private static final String SetCookieHeader = "Set-Cookie";
	private static final String CacheName = "OnlineAccountId";
	/**
	 * 持久化cookie的最长保存时间，单位毫秒
	 */
	private static final long CookieMaxAge = 365*24*3600*1000L;
	
	private static Log log = LogFactory.getLog(SessionSupport.class);

	private Cache onlineAccountCache = null;

	private AccountManager am = null;
	private EventSupport es = null;
	/**
	 * @param am
	 * @param timeout	临时会话过期时间, 单位分钟
	 */
	public SessionSupport(AccountManager am, int timeout, EventSupport es){
		this.am = am;
		this.es = es;
		onlineAccountCache = CacheManager.getInstance().getCache(CacheName);
		onlineAccountCache.getCacheConfiguration().timeToIdleSeconds(timeout*60);
		onlineAccountCache.getCacheEventNotificationService().getCacheEventListeners().add(new MyCacheEventListener(am, es));
		CacheUtils.storeDescription(onlineAccountCache, "用于跟踪系统会话");
	}
	
	/**
	 * 为用户创建临时会话，如果用户在设定时间内没有和服务器会话，则由服务器端终止会话
	 * @param response
	 * @param account	用户帐号，不能是系统内置的访客公共帐号
	 * @throws IllegalArgumentException		若帐号为空，或者是系统内置的访客公共帐号，抛出此异常
	 */
	public void createSession(HttpServletResponse response, Account account) throws IllegalArgumentException{
		if(account == null || account.getId() == InternalAccountProvider.getPublicGuestAccount().getId()){
			throw new IllegalArgumentException("帐号为空，或者为公共访客帐号");
		}
		CookieValue cookieValue = new CookieValue();
		cookieValue.sessionId = generalSessionId();
		String cookieSet = generalSetCookieHeader(cookieValue);
		Element element = new Element(cookieValue.sessionId, account.getId());
		onlineAccountCache.put(element);
		response.addHeader(SetCookieHeader, cookieSet);
		if(log.isDebugEnabled()){
			log.debug("创建临时会话, Cookie-Set" + cookieSet);
		}
		//上线事件 
		OnlineEvent event = new OnlineEvent(account, false);
		es.fireEvent(event);
	}
	
	/**
	 * 终止会话
	 * @param request
	 * @param response
	 */
	public void terminateSession(HttpServletRequest request, HttpServletResponse response){
		CookieValue cookieValue = getCookieValueFromHeader(request);
		Element element = onlineAccountCache.get(cookieValue.sessionId);
		Account account = null;
		if(element != null){
			int accountId = (Integer)element.getValue();
			account = Executor.execute(new AccountExecuteHandler(am,accountId));
			//下线事件
			OfflineEvent event = new OfflineEvent(account, false);
			es.fireEvent(event);
		}
		onlineAccountCache.remove(cookieValue.sessionId);
		String cookieSet = generalSetCookieHeader(null);
		response.addHeader(SetCookieHeader, cookieSet);
		if(log.isDebugEnabled()){
			log.debug("终止会话, Cookie-Set" + cookieSet);
		}
	}
	
	/**
	 * 为用户创建持久会话
	 * @param response
	 * @param account
	 * @throws IllegalArgumentException		若帐号为空，或者是系统内置的访客公共帐号，抛出此异常
	 */
	public void createPersistentSession(HttpServletResponse response, Account account){
		_createPersistentSession(response, account, null);
		//上线事件 
		OnlineEvent event = new OnlineEvent(account, false);
		es.fireEvent(event);
	}
	
	private void _createPersistentSession(HttpServletResponse response, Account account, String sessionId){
		if(account == null || account.getId() == InternalAccountProvider.getPublicGuestAccount().getId()){
			throw new IllegalArgumentException("帐号为空，或者为公共访客帐号");
		}
		CookieValue cookieValue = new CookieValue();
		cookieValue.sessionId = sessionId == null ? generalSessionId() : sessionId;
		cookieValue.accountId = account.getId();
		cookieValue.ciphertext = account.getPassword().getCiphertext();
		String cookieSet = generalSetCookieHeader(cookieValue);
		Element element = new Element(cookieValue.sessionId, account.getId());
		onlineAccountCache.put(element);
		response.addHeader(SetCookieHeader, cookieSet);
		if(log.isDebugEnabled()){
			log.debug("创建持久会话, Cookie-Set" + cookieSet);
		}
	}
	
	/**
	 * 获取当前会话中的用户帐号，如果没有建立会话，返回null
	 * @param request
	 * @param response
	 * @return
	 */
	public Account getAccount(HttpServletRequest request, HttpServletResponse response){
		final CookieValue cookieValue = getCookieValueFromHeader(request);
		Element element = null;
		if(cookieValue.sessionId != null)
			element = onlineAccountCache.get(cookieValue.sessionId);
		if(element != null){
			int accountId = (Integer)element.getValue();
			Account account = Executor.execute(new AccountExecuteHandler(am,accountId));
			return account;
		}
		
		if(cookieValue.ciphertext != null && cookieValue.accountId > 0){
			Account account = Executor.execute(new AccountExecuteHandler(am,cookieValue.accountId));
			if(account.getPassword().getCiphertext().equals(cookieValue.ciphertext)){
				_createPersistentSession(response, account, cookieValue.sessionId);
				//自动上线事件 
				OnlineEvent event = new OnlineEvent(account, true);
				es.fireEvent(event);
				return account;
			}
		}
		
		return null;
	}
	
	private static CookieValue getCookieValueFromHeader(HttpServletRequest request){
		CookieValue cookieValue = new CookieValue();
		Cookie[] cookies = request.getCookies();
		if(cookies != null)	{
			for(Cookie cookie : cookies){
				if(SessionName.equals(cookie.getName())){
					try{
						String value = cookie.getValue();
						if(value == null){
							break;
						}
						int length = value.length();
						if(length == 32){
							cookieValue.sessionId = value;
						} else if(length > 64){
							cookieValue.sessionId = value.substring(0, 32);
							cookieValue.accountId = Integer.parseInt(value.substring(32,length-32));
							cookieValue.ciphertext = value.substring(length-32);
						}	
					} catch(RuntimeException ex){
						cookieValue = new CookieValue();
					}
				}
			}
		}
		return cookieValue;
	}
	
	private static String generalSessionId(){
		String uuid = UUID.randomUUID().toString();
		StringBuffer sb = new StringBuffer(32);
		for(int i=0; i<uuid.length(); i++){
			char ch = uuid.charAt(i);
			if(ch != '-'){
				sb.append(ch);
			}
		}
		return sb.toString();
	}

	/**
	 * 生成会话cookie
	 * @param value
	 * @return
	 */
	private static String generalSetCookieHeader(CookieValue value){
		StringBuffer cookieSet = new StringBuffer();
		cookieSet.append(SessionName).append('=');
		//过期时间默认为空, 也就是不设置过期时间
		Date expireDate = null;
		if(value != null && value.sessionId != null){
			cookieSet.append(value.sessionId);
			if(value.ciphertext != null && value.accountId > 0){
				cookieSet.append(value.accountId).append(value.ciphertext);
				//设置过期时间
				expireDate = new Date(System.currentTimeMillis() + CookieMaxAge);
			}
		} else {
			//设置已经过期
			expireDate = new Date(0); 
		}
		
		cookieSet.append("; ");
		
		String path = ConfigRepository.instance().get(SiteConfig.class).getBaseURI().getPath();
		if(path == null || path.length() == 0){
			path = "/";
		} else if(path.charAt(path.length()-1) != '/'){
			path = path + '/';
		}
		
		cookieSet.append("path=")
				 .append(path)
				 .append("; ");
		
		if(expireDate != null){
			SimpleDateFormat format = new SimpleDateFormat("EEE, dd-MMM-yyyy hh:mm:ss z", Locale.ENGLISH);
			format.setTimeZone(TimeZone.getTimeZone("GMT"));
			String expires = format.format(expireDate);
			cookieSet.append("expires=").append(expires).append("; ");
		}		
		
		cookieSet.append("HttpOnly");
		
		return cookieSet.toString();
	}
	
	private static class CookieValue{
		private String sessionId;
		private String ciphertext;
		private int accountId;
	}
	
	private static class AccountExecuteHandler implements ExecuteHandler<Account>{

		private AccountManager am;
		private int accountId;
		private AccountExecuteHandler(AccountManager am, int accountId){
			this.am = am;
			this.accountId = accountId;
		}
		
		@Override
		public Account handle() {
			// TODO Auto-generated method stub
			Account account = am.getAccount(accountId);
			return account;
		}
		
	}
	
	private static class MyCacheEventListener implements CacheEventListener{
		
		private AccountManager am = null;
		private EventSupport es = null;
		MyCacheEventListener(AccountManager am, EventSupport es){
			this.am = am;
			this.es = es;
		}
		@Override
		public void dispose() {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void notifyElementEvicted(Ehcache arg0, Element arg1) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void notifyElementExpired(Ehcache cache, Element element) {
			// TODO Auto-generated method stub
			if(CacheName.equals(cache.getName())){
				int accountId = (Integer)element.getValue();
				Account account = Executor.execute(new AccountExecuteHandler(am,accountId));
				//自动下线事件
				OfflineEvent event = new OfflineEvent(account, true);
				es.fireEvent(event);
			}
		}

		@Override
		public void notifyElementPut(Ehcache arg0, Element arg1)
				throws CacheException {
			// TODO Auto-generated method stub
		}

		@Override
		public void notifyElementRemoved(Ehcache arg0, Element arg1)
				throws CacheException {
			// TODO Auto-generated method stub
		}

		@Override
		public void notifyElementUpdated(Ehcache arg0, Element arg1)
				throws CacheException {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void notifyRemoveAll(Ehcache arg0) {
			// TODO Auto-generated method stub

		}
		
		@Override
		public Object clone() throws CloneNotSupportedException{
			throw new CloneNotSupportedException();
		}
		
	}
	
}
